'''
= Goods Logistics =

This module provides for a way to coordinate transportation of goods
across the city.
'''

from concepts import *


class Logistics(Object):
    sources   = DictAttribute()
    sinks     = DictAttribute()
    buildings = DictAttribute()
    
    @property
    def good_types(self):
        return set(self.sources.keys()) | set(self.sinks.keys())
    
    def sources_for(self, good_type):
        if good_type.is_abstract:
            sources = set()
            
            for type in good_type.all_concrete_subtypes:
                sources |= self.sources_for(type)
            
            return sources
        elif good_type not in self.sources:
            return set()
        else:
            return set(self.sources[good_type])
    
    def sinks_for(self, good_type):
        if good_type.is_abstract or good_type not in self.sinks:
            return set()
        else:
            return set(self.sinks[good_type])
    
    def add_building(self, building):
        supply = building.supply
        demand = building.demand
        
        self._buildings[building] = (supply, demand)
        
        for good_type in supply:
            for subtype in good_type.all_concrete_subtypes:
                if subtype not in self._sources:
                    self._sources[subtype] = set()
                
                self._sources[subtype].add(Producer(building, subtype))
        
        for good_type in demand:
            for subtype in good_type.all_concrete_subtypes:
                if subtype not in self._sinks:
                    self._sinks[subtype] = set()
                
                self._sinks[subtype].add(Consumer(building, subtype))
    
    def remove_building(self, building):
        supply, demand = self._buildings[building]
        
        del self._buildings[building]
        
        for good_type in supply:
            for subtype in good_type.all_concrete_subtypes:
                if subtype not in self._sources:
                    continue
                
                self._sources[subtype].discard(Producer(building, subtype))
        
        for good_type in demand:
            for subtype in good_type.all_concrete_subtypes:
                if subtype not in self._sinks:
                    continue
                
                self._sinks[subtype].discard(Consumer(building, subtype))
    
    def update_building(self, building):
        self.remove_building(building)
        self.add_building(building)
    
    def __str__(self):
        string  = '== Logistics ==\n\n'
        string += 'Sources:\n'
        
        for good, sources in self.sources.items():
            string += '  - ' + good.singular + ': ' + ', '.join(str(source) for source in sources) + '\n'
        
        string += '\nSinks:\n'
        
        for good, sinks in self.sinks.items():
            string += '  - ' + good.singular + ': ' + ', '.join(str(sink) for sink in sinks) + '\n'
        
        string += '\nBuildings:\n'
        
        for building, items in self.buildings.items():
            supply, demand = items
            
            if not supply and not demand:
                continue
            
            string += '  - %s: Supply = {%s}, Demand = {%s}\n' % (building,
                                                                  ', '.join(good.singular for good in supply),
                                                                  ', '.join(good.singular for good in demand))
        
        return string


class TransportEndpoint(Object):
    building  = Attribute()
    good_type = Attribute()
    vehicles  = DictAttribute(initiable = False)

    @property
    def goods(self):
        pass
    
    @property
    def incoming_capacity(self):
        return sum(self.vehicles.values())
    
    @property
    def free_goods(self):
        return self.goods - self.incoming_capacity
    
    @property
    def quantity(self):
        return self.free_goods.quantity
    
    @property
    def needs_transport(self):
        return self.free_goods > 0
    
    @property
    def is_overcommitted(self):
        return self.free_goods < 0
    
    def add_incoming(self, vehicle, capacity):
        self._vehicles[vehicle] = capacity
    
    def remove_incoming(self, vehicle):
        if vehicle not in self._vehicles:
            return
        
        del self._vehicles[vehicle]
    
    def __hash__(self):
        return hash(self.building) + hash(self.good_type)
    
    def __eq__(self, other):
        return (self.building  == other.building and
                self.good_type == other.good_type)
    
    def __str__(self):
        return '%s(%s, %s)' % (self.__class__.__name__, self.building, self.good_type.plural)


class Producer(TransportEndpoint):
    @property
    def goods(self):
        return self.building.storage.all(self.good_type)


class Consumer(TransportEndpoint):
    @property
    def goods(self):
        return self.good_type(self.building.number_needed_of(self.good_type))
